#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/fs.h>

#define BUF_SIZE 512 //Size of buffer
#define USB1IMG "/dev/loop0" //First image file name
#define USB2IMG "/dev/loop1" //Second image file name
#define USB3IMG "/dev/loop2" //Parity image file name

void testGenerateParity();
void testRecover();
void testWriteMissingFile();

/*
 Function: Generates the parity image in third file descriptor given content of two other images.
 Params: fd0 - File descriptor for first image
	 fd1 - File descriptor for second image
 	 fp - File descriptor for parity image
 Returns: 1 - If the parity image is successfully generated
	  0 - If parity image cannot be generated
*/
int generateParity(int fd0, int fd1, int fp)
{
	return recover(fd0, fd1, fp);
}
/*
 Function: Checks if two read images are of same size and have same number of blocks required for recovery.
 Params: image1 - File descriptor for first image
	 iamge2 - File descriptor for second image
 Returns: 1 - If number of blocks contained in images are same.
	  0 - If number of blocks contained in images are not same.
*/
int checkImageSize(int image1, int image2)
{
	printf("Checking number of blocks in images...\n");
	unsigned long blocksImg1 = 0;
	unsigned long blocksImg2 = 0;
	ioctl(image1, BLKGETSIZE, &blocksImg1);
	ioctl(image2, BLKGETSIZE, &blocksImg2);
	printf("Number of blocks in image1: %lu; Number of blocks in image2: %lu.\n", blocksImg1, blocksImg2);
	if(blocksImg1 == blocksImg2)
	{
		printf("Block size check successful.\n");
		return 1;
	}
	else
	{
		printf("Block size check failed.\n");
		return 0;
	}
}

/*
 Function: Writes missing iamge in third file descriptor given content of other two images.
 Params: readFile1 - File descriptor for first image to be read
	 readFile2 - File descriptor for second image to be read
 	 writeFile - File descriptor for image that will be saved on the disk
 Returns: This function returns value of 1 if file saved successfully.
*/
int writeMissingFile(int readImage1, int readImage2, int writeImage)
{
	printf("Writing file...\n");
	char buffer0[BUF_SIZE], buffer1[BUF_SIZE]; 
	ssize_t ret_in;
	if(checkImageSize(readImage1, readImage2) == 0)
		return 0;
	while((ret_in = read(readImage1, &buffer0, BUF_SIZE)) > 0){
		read(readImage2, &buffer1, BUF_SIZE);
		size_t i = 0;
		for(; i < BUF_SIZE; i++) {
			char byte0 = ((char*)buffer0)[i];
			char byte1 = ((char*)buffer1)[i];
			char byteXor = byte0 ^ byte1;
			write(writeImage, &byteXor, sizeof(char));
		}
	}
	printf("File saved successfully.\n");
	return 1;
}

/*
 Function: Recovers lost file from remaining image and parity image.
 Params: fr1 - File descriptor for first image
	 fr2 - File descriptor for second image
 	 fw - File descriptor for parity image
 Returns: 1 - If the parity image is successfully generated
	  0 - If parity image cannot be generated
*/
int recover(int fr1, int fr2, int fw)
{
	if(fr1 == -1 || fr2 == -1 || fw == -1)
	{
		printf("Recovery failed.\nFile desriptors was not provided as expected.\nFileRead1: %d, FileRead2: %d, FileWrite: %d\n", fr1, fr2, fw);
		return 0; //File recovering failed.
	}	
	else // Create recovery
	{
		writeMissingFile(fr1, fr2, fw);
		return 1; //File recovered successfully
	}
}

/*
 Function: Shows usage message on console.
 Params: This function does not take any params.
 Returns: This function does not return any value.
*/
void showUsage()
{
	printf("raid5 usage:\n");
	printf("------------\n");
	printf("Parity generation mode:\n");
	printf("    raid5 --parity:\n\tSaves parity file on disk to device \"/dev/loop2\".\n\tUses images from device with names /dev/loop0 & /dev/loop1.\n\n");
	printf("    raid5 --parity --imageRead1 <fileName> --imageRead2 <fileName> --imageParity <fileName>:\n\tSaves parity file on disk with name provided as parameter \"fileParity\".\n\tUses image devices provided as \"fileRead1\" & \"fileRead2\".\n\n");
	printf("Recovery mode:\n");
	printf("    raid5 --recovery --imageRead1 <fileName> --imageRead2 <fileName> --imageWrite <fileName>:\n\tRecovers missing image using \"imageRead1\" & \"imageRead2\" as image file &\n\t\"imageParity\" as parity file.\n\n");
}
/*
 Function: Handles different program menu options.
 Params: operation - Operation being conducted or the mode of operation.
	 fileRead1 - Name of the first image
	 fileRead2 - Name of the second image
	 fileWrite - Name of the write image
 Returns: This function does not return any value.
*/
void modeOptions(char* operation, char* fileRead1, char* fileRead2, char* fileWrite)
{
	int fr1 = open(fileRead1, O_RDONLY, 0666);
	int fr2 = open(fileRead2, O_RDONLY, 0666);
	int fw = open(fileWrite, O_CREAT | O_RDWR, 0666);

	if(strcmp(operation, "--test") == 0 )
	{
		printf("Starting tests...\n");

		testGenerateParity();
		testRecover();
		testWriteMissingFile();

		printf("All tests passed.\n");
	}
	else if(strcmp(operation, "--parity") == 0 )
	{
		printf("Generating parity file.\n");
		generateParity(fr1, fr2, fw);		
	}
	else if(strcmp(operation, "--recovery") == 0 )
	{
		printf("Recovering image file.\n");
		recover(fr1, fr2, fw);
	}
	else
	{
		printf("Wrong params provided for operation.\n");
		showUsage();
	}

	close(fr1);
	close(fr2);        
	close(fw);
}

/*
 Function: Main entry point for this program.
 Params: argc - Number of arguments provided.
	 argv - Arguments passed while running program.
 Returns: This function returns "0" as success value.
*/
int main(int argc, char** argv) {
	
	if(argc == 2 && (strcmp(argv[1], "--parity") == 0 || strcmp(argv[1], "--test") == 0))
	{
		//Hardcoded file names
		modeOptions(argv[1], USB1IMG, USB2IMG, USB3IMG);
	}
	else if(argc == 8)
	{
		//Custom file names
		modeOptions(argv[1], argv[3], argv[5], argv[7]);
	}
	else	//Something doesn't look right. Showing usage message to user.
		showUsage();
	return 0;
}

/**************************** Test cases ***********************************/
void assertGreaterThanInt(char* testName, const int expected, const int actual);
void assertEqualsInt(char* testName, const int expected, const int actual);

void testGenerateParity()
{
	printf("Running test testGenerateParity()\n");
	int fd0 = open(USB1IMG, O_RDONLY, 0666);
	int fd1 = open(USB2IMG, O_RDONLY, 0666);
	int fp = open(USB3IMG, O_CREAT | O_RDWR, 0666);

	int retValue = generateParity(fd0, fd1, fp);
	close(fd0);
	close(fd1);
	close(fp);
	assertEqualsInt("testGenerateParity()", 1, retValue);
}
void testRecover()
{
	printf("Running test testRecover()\n");
	int fd0 = open(USB1IMG, O_CREAT | O_RDONLY, 0666);
	int fd1 = open(USB2IMG, O_RDONLY, 0666);
	int fp = open(USB3IMG, O_RDONLY, 0666);

	int retValue = recover(fd1, fp, fd0);
	close(fd0);
	close(fd1);
	close(fp);

	assertEqualsInt("testRecover()", 1, retValue);
}
void testWriteMissingFile()
{
	printf("Running test testWriteMissingFile()\n");
	int fd0 = open(USB3IMG, O_CREAT | O_RDWR, 0666);
	int fd1 = open(USB2IMG, O_RDONLY, 0666);
	int fp = open(USB3IMG, O_RDONLY, 0666);

	int retValue = writeMissingFile(fd1, fp, fd0);

	close(fd0);
	close(fd1);
	close(fp);

	assertEqualsInt("testWriteMissingFile()", 1, retValue);
}

void assertEqualsInt(char* testName, const int expected, const int actual)
{
	if(expected != actual)
	{
		printf("Assert Failed. Expected <%d> but was <%d> in %s.\n", expected, actual, testName);
		exit(1);	
	}
}

void assertGreaterThanInt(char* testName, const int expected, const int actual)
{
	if(expected >= actual)
	{
		printf("Assert Failed. Expected greater than <%d> but was <%d> in %s.\n", expected, actual, testName);
		exit(1);	
	}
}

